/*
*
==============================================================================
*  Name        : 
*  Part of     : Omen Game Engine
*  Description : 
*  Version     : 1.0
*
*  Copyright (c) 2013 Lauri Kortevaara.
* 
==============================================================================
*/

#define OMEN_SOURCE 1

/// Omen headers
#include "OmenModelFileReader.h"
#include <OmenXmlReader.h>
#include <OmenUtils.h>

#include "OmenScene.h"
#include "OmenCamera.h"
#include "OmenModel.h"
#include "OmenMesh.h"

/// STL Headers
#include <iostream>
#include <fstream>
#include <istream>
#include <ios>
#include <ostream>
#include <sstream>
#include <algorithm>

using namespace Omen;

///
/// <summary></summary>
///
ModelFileReader::ModelFileReader()
    : m_modelFileName(L""),
      m_fileFormat(L""),
      m_fileFormatVersion(L""),
      m_pCurrentScene(NULL),
      m_pCurrentModel(NULL)
{
}


///
/// <summary></summary>
///
ModelFileReader::ModelFileReader( const ModelFileReader& other )
{
    m_modelFileName = other.m_modelFileName;
    m_fileFormat = other.m_fileFormat;
    m_fileFormatVersion = other.m_fileFormatVersion;

    m_listScenes = other.m_listScenes;
    m_pCurrentScene = other.m_pCurrentScene;
}

///
/// <summary></summary>
///
ModelFileReader::~ModelFileReader()
{
}

///
/// <summary></summary>
///
unsigned long ModelFileReader::getSceneCount() const
{
    return m_listScenes.size();
}

///
/// <summary></summary>
///
bool ModelFileReader::canRead( const std::wstring& filename )
{
    bool bCanRead = false;

    std::wifstream fin;
    
    // Try to open the file
    if( filename.find( L".omf" ) != std::wstring::npos )
        fin.open( filename );

    if( !fin.fail() && fin.is_open() )
    {
        // Close the file stream
        fin.close();

        Omen::XmlReader fileReader( filename );
        if( fileReader.parseHeader() )
        {
            fileReader.getDocument( m_model_doc );
            if( m_model_doc.begin()->name().compare( L"omen-model-file" ) == 0 )
            {
                XmlNodeAttribute fileFormat;
                if( m_model_doc.begin()->getAttribute( L"format", fileFormat ) )
                {
                    m_fileFormat = fileFormat.value();
                }
                XmlNodeAttribute fileVersion;
                if( m_model_doc.begin()->getAttribute( L"version", fileVersion ) )
                {
                    m_fileFormatVersion = fileVersion.value();
                }
                if( modelFileFormatVersionCompatible( m_fileFormatVersion ) )
                {
                    bCanRead = true;
                    m_modelFileName = filename;
                }
            }
            m_model_doc.clear();
        }

    }
    return bCanRead;
}

bool ModelFileReader::loadFile()
{
    bool bResult = false;
    if( m_fileFormat == L"xml" )
    {
        bResult = loadFileXml();
    }
    else
    if( m_fileFormat == L"binary" )
    {
        bResult = loadFileBinary();
    }

    if( bResult )
    {
        // Now update the loaded models vertex normals
        for( auto iScene = m_listScenes.begin(); iScene != m_listScenes.end(); iScene++ )
        {
            unsigned long modelCount = (*iScene)->modelCount();
            std::unique_ptr<Omen::IModel*> listModels(new Omen::IModel*[modelCount]);
            if( listModels )
            {
                (*iScene)->getModels(listModels.get(), modelCount);
                for( unsigned long iModel = 0; iModel < modelCount; ++iModel )
                {
                    if( !listModels.get()[iModel] )
                        continue;
                    unsigned long meshCount = listModels.get()[iModel]->meshCount();
                    std::unique_ptr<Omen::IMesh*> listMeshes( new Omen::IMesh*[meshCount] );
                    if( listMeshes )
                    {
                        listModels.get()[iModel]->getMeshes( listMeshes.get(), meshCount );
                        for( unsigned long iMesh = 0; iMesh < meshCount; ++iMesh )
                        {
                            if( listMeshes.get()[iMesh] )
                            {
                                Omen::IMesh* pMesh = listMeshes.get()[iMesh];
                                if( pMesh )
                                    pMesh->updateVertexNormals();
                            }
                        }
                    }
                }
            }
        }
    }
    return bResult;
}

bool ModelFileReader::loadFileBinary()
{
    return false; /// No implementation yet
}

///
/// <summary>Load the xml-formatted Omen Model File</summary>
///
bool ModelFileReader::loadFileXml()
{
    Omen::XmlReader reader(this->m_modelFileName);

    if( reader.parseDocument() )
    {
        XmlDocument doc;
        if( reader.getDocument( doc ) )
        {
            for( auto rootNode = doc.begin(); rootNode!= doc.end(); ++rootNode )
            {
                XmlNode node = *rootNode;
                parseModelFileNode(node);
            }
        }
        
    }
    else
        return false;

    return true;
}

/// <summary>Parses the given string and returns a x,y,z Omen::Vector3d vector out of it</summary>
static Omen::Vector3d getXYZVector( std::wstring& str )
{
    std::wstringstream ss( str );
    std::wstring field;
    Omen::Float x = 0.0, y =0.0, z =0.0;

    unsigned int fpos = 0;
    while (std::getline( ss, field, L' ' ))
    {
        std::wstringstream fs( field );
        Omen::Float f = 0.0;  // (default value is 0.0)
        fs >> f;

        switch( fpos )
        {
        case 0: x = f; break;
        case 1: y = f; break;
        case 2: z = f; break;
        default: break;
        }

        fpos++;
    }
    return Omen::Vector3d(x,y,z);
}

bool ModelFileReader::parseSceneNode( Omen::XmlNode& node, Omen::IScene*& pScene )
{
    std::wstring nodeName = node.name();
    std::wstring nodeData = node.data();

    std::vector<Omen::XmlNode>& children = node.children();
    std::vector<Omen::XmlNodeAttribute>& attributes = node.attributes();

    // Handle scene
    if( nodeName == L"camera" )
    {
        Omen::Vector3d cameraPos, cameraLookAt, cameraUp, cameraRight;

        std::wstring strCameraPos;
        std::wstring strCameraLookAt;
        std::wstring strCameraUp;
        std::wstring strCameraRight;

        Omen::Camera* pCamera = new Omen::Camera;
        pScene->addCamera( pCamera );

        if( pCamera )
        for( auto iter = attributes.begin(); iter != attributes.end(); ++iter )
        {
            XmlNodeAttribute attribute = *iter;
            pCamera->setProperty( attribute.name(), attribute.value() );
        }

        // Parse Attributes
        for( auto attrib= attributes.begin(); attrib!= attributes.end(); ++attrib )
        {
            Omen::XmlNodeAttribute& attribute = *attrib;
            std::wstring& attribName = attribute.name();
            std::wstring& attribValue = attribute.value();
            
            if( attribName.compare( L"position" ) == 0 )
                strCameraPos = attribValue;
            else
            if( attribName.compare( L"right" ) == 0 )
                strCameraRight =  attribValue;
            else
            if( attribName.compare( L"up" ) == 0 )
                strCameraUp =  attribValue;
            else
            if( attribName.compare( L"look-at" ) == 0 )
                strCameraLookAt =  attribValue;
        }

        std::vector<Omen::Float> v;
        getFloatsVector( strCameraPos, v );
        
        cameraPos = getXYZVector( strCameraPos );
        cameraLookAt = getXYZVector( strCameraLookAt );
        cameraUp = getXYZVector( strCameraUp );
        cameraRight = getXYZVector( strCameraRight );
        
        pCamera->setPosition( cameraPos );
        pCamera->setRight( cameraRight );
        pCamera->setUp( cameraUp );
        pCamera->setForward( cameraLookAt );
    }

    if( nodeName == L"model" )
    {
        Omen::IModel* pModel = NULL;
        parseModelNode( node, pModel );
        if( pModel != NULL )
            pScene->addModel(pModel);
        return true;
    }

    // Parse child nodes
    for( auto child= children.begin(); child!= children.end(); ++child )
    {
        Omen::XmlNode& childNode = *child;
        parseSceneNode( childNode, pScene );
    }
    return true;
}

bool ModelFileReader::parseModelNode( Omen::XmlNode& node, Omen::IModel*& pModel )
{
    std::wstring nodeName = node.name();
    std::wstring nodeData = node.data();

    std::vector<Omen::XmlNode>& children = node.children();
    std::vector<Omen::XmlNodeAttribute>& attributes = node.attributes();

    // If the model is defined in another file, load it's content
    if( std::find( attributes.begin(), attributes.end(), L"source" ) != attributes.end() )
    {
        Omen::XmlNodeAttribute sourceAttrib = *std::find( attributes.begin(), attributes.end(), L"source" );
        std::wstring filename = sourceAttrib.value();

        // Get the Omen Interface factory
        IOmenFactory& pFactory = GetOmenFactory();

        // Now as we've got the factory, get the kernel pointer
        Omen::IKernel& pKernel = pFactory.getKernel();
        
        // Get the data directory
        std::wstring data_dir = pKernel.getProperty( L"data-dir" );
        
        Omen::IModelFileReader* pReader = pKernel.getStorageServer()->getModelFileReader(data_dir+L"/"+filename);
        // If a compatible reader was file, load the model-file
        if( pReader )
        {
            if( pReader->loadFile() )
            {
                // Add
                if( pReader->getSceneCount() > 0 )
                {
                    // Allocate a list of Scene pointers to impor the loaded scenes
                    Omen::IScene** pScenes = new Omen::IScene*[pReader->getSceneCount()];
                    if( pScenes )
                    {
                        // Get the scenes from the reader
                        pReader->getScenes(pScenes, pReader->getSceneCount());

                        for( unsigned long iScene = 0; iScene < pReader->getSceneCount(); ++iScene )
                        {
                            Omen::IScene* pScene = pScenes[iScene];
                            if( pScene )
                            {
                                // Merge model attributes
                                for( auto attribIter = attributes.begin(); attribIter != attributes.end(); ++attribIter )
                                for( unsigned long iModel = 0; iModel < pScene->modelCount(); ++iModel )
                                {
                                    Omen::IModel* pModel = pScene->getModel(iModel);
                                    if( pModel )
                                    {
                                        // Add only missing attributes/properties
                                        std::wstring propertyValue;
                                        pModel->getProperty(attribIter->name(),propertyValue);
                                        if( propertyValue.empty() )
                                            pModel->setProperty( attribIter->name(), attribIter->value() );
                                    }
                                }

                                // If we currently have a parent scene defined, instead of importing the scene from the file,
                                // Add the models from the scene to the current scene
                                if( m_pCurrentScene )
                                {
                                    m_pCurrentScene->mergeScene(pScene);
                                }
                                else
                                    m_listScenes.push_back(pScene);
                            }
                        }
                    }
                }
            }
        }
    }

    if( nodeName == L"mesh" )
    {
        if( !pModel )
            pModel = new Omen::Model;
        m_pCurrentModel = pModel;

        Omen::IMesh* pMesh = new Omen::Mesh;
        parseMeshNode(node, pMesh);
        pModel->addMesh(pMesh);
    }

    if( nodeName == L"rigidbody" )
    {
        if( !pModel )
            pModel = new Omen::Model;
        m_pCurrentModel = pModel;
        // Get the Omen Interface factory
        IOmenFactory& pFactory = GetOmenFactory();
        std::unique_ptr<Omen::IRigidBody> pRigidBody = pFactory.newRigidBody();
    }

    // Add the model properties from the file
    if( m_pCurrentModel )
    for( auto iter = attributes.begin(); iter != attributes.end(); ++iter )
    {
        XmlNodeAttribute attribute = *iter;
        m_pCurrentModel->setProperty( attribute.name(), attribute.value() );
    }

    // Parse child nodes
    for( auto child= children.begin(); child!= children.end(); ++child )
    {
        Omen::XmlNode& childNode = *child;
        parseModelNode( childNode, pModel );
    }
    return true;
}

bool ModelFileReader::parseMeshNode( Omen::XmlNode& node, Omen::IMesh*& pMesh )
{
    std::wstring nodeName = node.name();
    std::wstring nodeData = node.data();

    std::vector<Omen::XmlNode>& children = node.children();
    std::vector<Omen::XmlNodeAttribute>& attributes = node.attributes();

    // Handle mesh
    if( nodeName == L"mesh" )
    {
        // Parse Attributes
        for( auto attrib= attributes.begin(); attrib!= attributes.end(); ++attrib )
        {
            Omen::XmlNodeAttribute& attribute = *attrib;
            std::wstring& attribName = attribute.name();
            std::wstring& attribValue = attribute.value();
            pMesh->setProperty( attribName, attribValue );
            if( attribName == L"texture" )
            {
                // Get the Omen Interface factory
                IOmenFactory& pFactory = GetOmenFactory();

                // Now as we've got the factory, get the kernel pointer
                Omen::IKernel& pKernel = pFactory.getKernel();
        
                // Get the data directory
                std::wstring data_dir = pKernel.getProperty( L"data-dir" );
        
                Omen::IImageFileReader* pReader = pKernel.getStorageServer()->getImageFileReader(data_dir+L"/"+attribValue);
                if( pReader )
                {
                    ITexture* pTexture = pReader->createTexture(data_dir+L"/"+attribValue);
                    if( pTexture )
                        pMesh->setTexture(pTexture);
                }
            }
        }
    }
    else
    // Handle mesh vertices
    if( nodeName == L"vertices" )
    {
        Omen::Vector3d cameraPos, cameraLookAt, cameraUp;

        std::wstring strVertexSchema;

        // Parse Attributes
        for( auto attrib= attributes.begin(); attrib!= attributes.end(); ++attrib )
        {
            Omen::XmlNodeAttribute& attribute = *attrib;
            std::wstring& attribName = attribute.name();
            std::wstring& attribValue = attribute.value();
            if( attribName.compare( L"schema" ) == 0 )
                strVertexSchema = attribValue;
        }
    }

    if( nodeName == L"faces" )
    {
        Omen::Vector3d cameraPos, cameraLookAt, cameraUp;

        std::wstring strVertexSchema;

        // Parse Attributes
        for( auto attrib= attributes.begin(); attrib!= attributes.end(); ++attrib )
        {
            Omen::XmlNodeAttribute& attribute = *attrib;
            std::wstring& attribName = attribute.name();
            std::wstring& attribValue = attribute.value();
            if( attribName.compare( L"schema" ) == 0 )
                strVertexSchema = attribValue;
        }
    }

    if( nodeName == L"vertex-data" )
    {
        std::vector<Omen::Float> vertexData;
        getFloatsVector( node.data(), vertexData );
        
        IMesh::Vertex v;

        if( vertexData.size() >= 3 )
        {
            v.x = vertexData[0];
            v.y = vertexData[1];
            v.z = vertexData[2];
        }
        
        if( vertexData.size() >= 5 )
        {
            v.tu = vertexData[3];
            v.tv = vertexData[4];
        }

        if( vertexData.size() >= 8 )
        {
            v.nx = vertexData[5];
            v.ny = vertexData[6];
            v.nz = vertexData[7];
        }
        
        pMesh->addVertex(v);
    }

    if( nodeName == L"index-data" )
    {
        std::vector<unsigned long> faceData;
        getLongsVector( node.data(), faceData );
        
        IMesh::Face face;

        if( faceData.size() == 3 )
        {
            face.p1 = faceData[0];
            face.p2 = faceData[1];
            face.p3 = faceData[2];
        }
        
        pMesh->addFace( face );
    }
    // Parse child nodes
    for( auto child= children.begin(); child!= children.end(); ++child )
    {
        Omen::XmlNode& childNode = *child;
        parseMeshNode( childNode, pMesh );
    }
    return true;
}

/// <summary>Parse the model-file nodes</summary>
bool ModelFileReader::parseModelFileNode( Omen::XmlNode& node )
{
    std::wstring nodeName = node.name();
    std::wstring nodeData = node.data();

    std::vector<Omen::XmlNode>& children = node.children();
    std::vector<Omen::XmlNodeAttribute>& attributes = node.attributes();

    // Handle scene
    if( nodeName == L"scene" )
    {
        Omen::IScene* pNewScene = new Omen::Scene();
        m_pCurrentScene = pNewScene;
        parseSceneNode( node, pNewScene );
        m_listScenes.push_back(pNewScene);
        m_pCurrentScene = NULL;
    }

    if( nodeName == L"property" )
    {
        std::wstring propertyName;
        std::wstring propertyValue;

        // Parse Attributes
        for( auto attrib= attributes.begin(); attrib!= attributes.end(); ++attrib )
        {
            Omen::XmlNodeAttribute& attribute = *attrib;
            std::wstring& attribName = attribute.name();
            std::wstring& attribValue = attribute.value();
            if( attribName.compare( L"name" ) == 0 )
                propertyName = attribValue;
            else
            if( attribName.compare( L"value" ) == 0 )
                propertyValue =  attribValue;
        }
        
        // Add new kernel property
        //this->setProperty( propertyName, propertyValue );
    }

    // Parse child nodes
    for( auto child= children.begin(); child!= children.end(); ++child )
    {
        Omen::XmlNode& childNode = *child;
        parseModelFileNode( childNode );
    }

    return true;
}

/// <summary></summary>
bool ModelFileReader::getScenes( IScene** const& pListModels, unsigned int maxSize ) const
{
    for( unsigned int i=0; i < m_listScenes.size(); ++i )
    {
        pListModels[i] = m_listScenes.at(i);
    }
    return true;
}

/// <summary>modelFileFormatVersionCompatible returns true if the model file version is supported by this engine version</summary>
bool ModelFileReader::modelFileFormatVersionCompatible( const std::wstring& version )
{
    if( version.compare(ModelFileFormat_Version_1_0) == 0 ) 
        return true;

    return false;
}